<template>
  <div class="ee-guide" @click="click">
    <svg :class="'mask' + (__through ? ' through' : '')">
      <mask id="mask">
        <rect class="mask" />
        <g v-for="(i, index) in __areas" :key="index">
          <rect
            v-if="!i.type"
            class="hollow"
            :width="i.w"
            :height="i.h"
            :x="i.x"
            :y="i.y"
          />
          <circle
            v-else-if="i.type == 1"
            class="hollow"
            :cx="i.cx"
            :cy="i.cy"
            :r="i.r"
          />
          <slot v-else name="mask" class="hollow" :i="i" :index="index"></slot>
        </g>
      </mask>
      <rect class="mask result" />
    </svg>
    <template v-for="(i, index) in __areas" :key="index">
      <div v-if="$slots.svg || i.template"
        class="hollow"
        :style="{ left: i.x + 'px', top: i.y + 'px' }"
      >
        <div v-if="i.template" v-html="i.template"></div>
        <slot name="svg" :i="i" :index="index"></slot>
      </div>
    </template>
    <slot :i="this"></slot>
  </div>
</template>

<script type="text/javascript">
function dom(value) {
  if (value.constructor == String)
    return document.querySelector(value)
  else if (value.$el)
    return value.$el;
  else
    return value;
}
function area(value) {
  if (value.constructor == String)
    value = { dom: document.querySelector(value) };
  else if (value instanceof HTMLElement)
    value = { dom: value };
  else if (value.$el)
    value = { dom: value.$el };
  else
    value.dom = dom(value.dom);
  // if (value.template)
  //   value.template = dom(dom.template);
  return value;
}
export default injectRender({
  name: "ee-guide",
  emits: ["onclose"],
  data() {
    return {
      __through: false,
    };
  },
  props: {
    /** doms元素大小位置是否是动态变化的(动态变化的会开计时器不断检测dom元素区域变化) */
    dynamic: Boolean,
    /** 弹出引导时是否还允许滚动窗口 */
    // scrollable: Boolean,
    /** 弹出引导时doms区域是否可以让鼠标穿透进行操作 */
    clickable: Boolean,
    /** 镂空dom元素
     * 可以是document.querySelector的字符串，dom元素，组件，对象数组或以上类型的数组
     * 最终都会转成对象数组，对象属性如下
     * 镂空区域：x y w(宽) h(高) cx(中点横坐标) cy(中点纵坐标) r(外接圆半径，矩形对角线的一半) type(镂空类型 0:矩形 / 1:圆形 / 2:自定义svg形状)
     * dom: dom元素，传入时可以是document.querySelector的字符串，会被转换成dom元素
     * contains(x, y): 形状内是否包含鼠标坐标的方法，返回非空代表包含，主要用于计算clickable的区域，常用于area为自定义svg形状(默认使用矩形计算方法)或单独不想让区域被点击时
     * valid: 验证是否可以进行下一步的方法，返回非空或Promise.Resolve代表可以，必须所有valid都通过才算通过，不传则默认通过
     * template: 渲染在#svg模板前面的dom元素，可赋值对象参见dom属性
     * focus: dom可能不在视图区域内，增加focus可以滚动滚动条，让dom出现在视图区域内；若有多个focus，仅第一个生效；若没有dom仅有镂空区域也可以
     */
    areas: {
      type: [String, Array, Object],
      default: () => [],
      set(value) {
        if (value.constructor == String)
          this.__areas = [area(value)];
        else if (value.constructor == Array)
          for (let i = 0; i < value.length; i++)
            value[i] = area(value[i]);
        else
          this.__areas = [area(value)];
        this.cut();
      },
    },
    /** 指定Function或Promise以结束当前引导
     * Function: 返回非空值代表结束
     * Promise: Resolve代表结束
     * 例如可以是文本框必须输入正确值，必须点击下一步或跳过按钮等自定义方法
     * 如果不指定，则点击任意areas区域内都会结束
     */
    onNext: {},
  },
  methods: {
    through(e) {
      if (!this.clickable) return;
      for (let i of this.__areas) {
        if (i.contains) {
          // 自定义区域检查
          if (!i.contains(e.x, e.y)) continue;
        } else {
          // 圆形区域检查
          if (i.type == 1) {
            let x = e.x - i.cx;
            let y = e.y - i.cy;
            if (x * x + y * y >= i.r * i.r) continue;
          }
          // 矩形区域检查
          else if (
            e.x < i.x ||
            e.y < i.y ||
            e.x >= i.x + i.w ||
            e.y >= i.y + i.h
          )
            continue;
        }
        this.__through = true;
        return;
      }
      this.__through = false;
    },
    cut() {
      // 计算出doms所在区域
      for (let i of this.__areas) {
        if (i.dom) {
          let box = i.dom.getBoundingClientRect();
          i.x = box.x;
          i.y = box.y;
          i.w = box.right - i.x;
          i.h = box.bottom - i.y;
          i.cx = i.x + i.w * 0.5;
          i.cy = i.y + i.h * 0.5;
          i.r = Math.sqrt(i.w * i.w + i.h + i.h) * 0.5;
        }
        // this.__areas[index++] = i;
        if (i.focus) {
          if (i.dom) i.dom.scrollIntoView();
          else {
            // 滚动到指定镂空区域：document.documentElement.scrollTo
            // 可滚动范围 = document.documentElement.scrollHeight - document.documentElement.clientHeight
            // todo: dom可能在非跟元素的一个可滚动的dom容器内，此时应该滚动的是该容器
            // todo: 注意，如果使用x,y，滚动过去之后x,y发生了变化，所以要使用一个跟滚动条无关的坐标属性offsetLeft和offsetTop
            // window.scrollTo(
            //   i.dom.offsetLeft - window.innerWidth * .5,
            //   i.dom.offsetTop - window.innerHeight * .5,
            // )
          }
          break;
        }
      }
      if (this.dynamic) this.__timer = setTimeout(this.cut, 50);
    },
    onscroll(e) {
      this.cut();
    },
    valid(i) {
      console.log("click", i);
    },
    close() {
      console.log("close")
      this.$emit("onclose");
    },
    click() {
      if (!this.onNext)
        this.close();
    },
  },
  mounted() {
    window.addEventListener("mousemove", this.through);
    window.addEventListener("scroll", this.onscroll);
  },
  unmounted() {
    clearTimeout(this.__timer);
    window.removeEventListener("scroll", this.onscroll);
    window.removeEventListener("mousemove", this.through);
  },
});
</script>

<style lang="scss" scoped>
.ee-guide {
  position: fixed;
  left: 0;
  top: 0;

  .through {
    pointer-events: none;
  }

  .mask {
    position: absolute;
    left: 0;
    top: 0;
    width: 100vw;
    height: 100vh;
    fill: #fff;
  }

  .hollow {
    position: absolute;
    // transition: all .2s;
    fill: #000;
  }

  .result {
    fill: #00000080;
    mask: url(#mask);
  }
}
</style>